package com.lhl.mq.mqServer.datacenter;

import com.lhl.mq.common.MqException;
import com.lhl.mq.mqServer.core.Binding;
import com.lhl.mq.mqServer.core.Exchange;
import com.lhl.mq.mqServer.core.MSGQueue;
import com.lhl.mq.mqServer.core.Message;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 使用这个类来统一管理内存中的数据
 * 该类提供的一些方法可能会在多线程的环境下使用，要注意线程安全问题
 */
public class MemoryDataCenter {
    //交换机，key-exchangeName value-Exchange对象
    private ConcurrentHashMap<String, Exchange> exchangeMap = new ConcurrentHashMap<>();
    //队列，key-queueName value-MSGQueue
    private ConcurrentHashMap<String, MSGQueue> queueMap = new ConcurrentHashMap<>();
    //绑定，key-exchangeName value-key-queueName value-value-Binding对象
    private ConcurrentHashMap<String,ConcurrentHashMap<String, Binding>> bindingsMap = new ConcurrentHashMap<>();
    //消息,key-messageId valueMessage对象
    private ConcurrentHashMap<String, Message> messageMap = new ConcurrentHashMap<>();
    //队列与消息的连接,key-queueName value-LinkedList<Message>
    private ConcurrentHashMap<String, LinkedList<Message>> queueMessageMap = new ConcurrentHashMap<>();
    //"未被确认的消息" key-queueName value-key-messageId value-value-Message对象
    private ConcurrentHashMap<String,ConcurrentHashMap<String,Message>> queueMessageWaitAckMap = new ConcurrentHashMap<>();

    //操作交换机
    public Exchange getExchange(String exchangeName){
        return exchangeMap.get(exchangeName);
    }

    public void insertExchange(Exchange exchange){
        exchangeMap.put(exchange.getName(),exchange);
        System.out.println("[MemoryDataCenter] 添加交换机成功，exchangeName = " + exchange.getName());
    }

    public void deleteExchange(String exchangeName){
        exchangeMap.remove(exchangeName);
        System.out.println("[MemoryDataCenter] 删除交换机成功，exchangeName = " + exchangeName);
    }

    //操作队列
    public MSGQueue getQueue(String queueName){
        return queueMap.get(queueName);
    }

    public void insertQueue(MSGQueue queue){
        queueMap.put(queue.getName(),queue);
        System.out.println("[MemoryDataCenter] 插入队列成功，queueName = " + queue.getName());
    }

    public void deleteQueue(String queueName){
        queueMap.remove(queueName);
        System.out.println("[MemoryDataCenter] 队列删除成功，queueName = " + queueName);
    }

    //操作绑定
    public void insertBinding(Binding binding) throws MqException {
        //判断、对应的哈希表是否存在，不存在的话就创建一个
        ConcurrentHashMap<String,Binding> bindingMap = bindingsMap.computeIfAbsent(binding.getExchangeName(),
                k -> new ConcurrentHashMap<>());

        synchronized (bindingMap) {
            //判断通过queueName是否能找到Binding，如果可以则插入失败
            if (bindingMap.get(binding.getQueueName()) != null){
                throw new MqException("[MemoryDataCenter] 绑定已存在，exchangeName = " + binding.getExchangeName()
                 + ", queueName = " + binding.getQueueName() );
            }
            //进行数据的插入
            bindingMap.put(binding.getQueueName(),binding);
        }

        System.out.println("[MemoryDataCenter] 插入绑定成功，exchangeName = " + binding.getExchangeName() +
                ", queueNme = " + binding.getQueueName());
    }

    //提供两个查找绑定的方法
    //1、根据交换机名字和队列名字去查找唯一的绑定
    //2、查询交换机下全部的绑定
    public Binding getBinding(String exchangeName,String queueName) throws MqException {
        //先查询哈希表是否存在
        ConcurrentHashMap<String,Binding> bindingMap = bindingsMap.get(exchangeName);
        if (bindingMap == null){
           return null;
        }

        //根据队列名来查询绑定
        return bindingMap.get(queueName);
    }

    public ConcurrentHashMap<String, Binding> getBindings(String exchangeNme){
        return bindingsMap.get(exchangeNme);
    }

    public void deleteBinding(Binding binding) throws MqException {
        ConcurrentHashMap<String, Binding> bindingMap = bindingsMap.get(binding.getExchangeName());
        if (bindingMap == null){
          // 该交换机没有绑定任何的队列
            throw new MqException("[MemoryDataCenter] 绑定不存在，exchange = " + binding.getExchangeName());
        }
        bindingMap.remove(binding.getQueueName());
        System.out.println("[MemoryDataCenter] 删除绑定成功， exchangeName = " + binding.getExchangeName()
         + ", queueName = " + binding.getQueueName());
    }

    //操作消息
    public Message getMessage(String messageId){
        return messageMap.get(messageId);
    }

    public void addMessage(Message message){
        messageMap.put(message.getMessageId(),message);
        System.out.println("[MemoryCenter] 添加消息成功");
    }

    public void deleteMessage(String messageId){
        messageMap.remove(messageId);
        System.out.println("[MemoryDataCenter] 消息删除成功，messageId = " + messageId);
    }

    //操作队列与消息的关联
    //发送消息到指定队列
    public void senMessage(MSGQueue queue,Message message){
        //将消息发送到对应的数据结构里面
        //根据队列名，找到该队列对应的消息链表
        LinkedList<Message> messages = queueMessageMap.computeIfAbsent(queue.getName(), k -> new LinkedList<>());
        //将消息添加到消息链表
        synchronized (message) {
            messages.add(message);
        }
        //将该消息也添加一份到消息中心，就算现在消息在消息中心存在也无所谓
        //主要是要保证 messageId 在不同的地方找到的消息内容是要是一样的
        messageMap.put(message.getMessageId(),message);

        System.out.println("[MemoryDataCenter] 消息成功投递到队列中，queueName = " + queue.getName()
                +  ", messageId = " + message.getMessageId());
    }

    //从队列中取消息(根据队列名从消息链表里取出第一个元素，并进行删除)
    public Message pollMessage(String queueName){
        //根据队列名查找消息链表
        LinkedList<Message> messages = queueMessageMap.get(queueName);
        //如果不存在则返回null
        if (messages == null){
            return null;
        }
        synchronized (messages) {
            //如果链表长度为0，也返回null
            if (messages.isEmpty()){
                return null;
            }
            //将消息链表里面的第一个消息取出并删除
            Message message = messages.remove(0);
            System.out.println("[MemoryDtaCenter] 消息成功取出，queueName = " + queueName + ", messageId = " + message.getMessageId());
            //返回取出来的消息
            return message;
        }
    }

    //获取队列中的消息数量
    public int getMessageCount(String queueName){
        //根据队列名取出消息链表
        LinkedList<Message> messages = queueMessageMap.get(queueName);
        //链表不存在返回0
        if (messages == null){
            return 0;
        }
        //返回链表长度
        synchronized (messages) {
            int size = messages.size();
            System.out.println("[MessageDataCenter] 成功获取链表长度，queueName = " + queueName);
            return size;
        }
    }

    //操作“未确认”消息
    //添加未确认消息
    public void addQueueMessageWaitAckMap(String queueName,Message message){
        //查看消息的哈希表是否存在，不存在则创建
        ConcurrentHashMap<String, Message> messages = queueMessageWaitAckMap.computeIfAbsent(queueName, k -> new ConcurrentHashMap<>());

        //添加消息到哈希表中
        messages.put(message.getMessageId(),message);
        System.out.println("[MessageDataCenter] 成功投递消息到未确认消息队列中，queueName = " + queueName + ", messageId = " + message.getMessageId());
    }

    //删除未确认消息（消息已被确认）
    public void  deleteQueueMessageWaitAckMap(String queueName,String messageId){
        //查找未确认消息的哈希表
        ConcurrentHashMap<String, Message> messages = queueMessageWaitAckMap.get(queueName);
        //哈希表不存在则直接返回
        if (messages == null){
            return;
        }
        //根据消息id删除哈希表里的消息
        messages.remove(messageId);
        System.out.println("[MessageDatacenter] 成功删除未被确认消息，queueName = " + queueName
                + ", messageId = " + messageId);

    }

    //获取指定的未被确认消息
    public Message getQueueMessageWaitAck(String queueName,String messageId){
        ConcurrentHashMap<String, Message> messages = queueMessageWaitAckMap.get(queueName);
        if (messages == null){
            return null;
        }

        Message message = messages.get(messageId);
        System.out.println("[MessageDataCenter] 成功获取未被确认的消息，queueName = " + queueName + ", messageId = " + messageId);
        return message;
    }

    //从硬盘中获取数据恢复到内存中(服务器重启之后，内存里面的数据会被清空清空)
    public void recovery(DiskDataCenter diskDataCenter) throws MqException, IOException, ClassNotFoundException {
        //清空之前的所有数据
        exchangeMap.clear();
        queueMap.clear();
        bindingsMap.clear();
        messageMap.clear();

        //从硬盘中恢复数据
        //恢复交换机的数据
        List<Exchange> exchanges = diskDataCenter.selectAllExchanges();
        for (Exchange exchange : exchanges){
            exchangeMap.put(exchange.getName(), exchange);
        }

        //恢复队列的数据
        List<MSGQueue> msgQueues = diskDataCenter.selectAllQueues();
        for (MSGQueue queue : msgQueues){
            queueMap.put(queue.getName(), queue);
        }

        //恢复绑定的数据
        List<Binding> bindings = diskDataCenter.selectAllBindings();
        for (Binding binding : bindings) {
            ConcurrentHashMap<String, Binding> bindingMap = bindingsMap.computeIfAbsent(binding.getExchangeName(),
                    k -> new ConcurrentHashMap<>());
            bindingMap.put(binding.getQueueName(), binding);
        }

        //恢复消息的数据
        //消息存储在队列里面
        //加载队列全部的消息
        for (MSGQueue queue : msgQueues){
            LinkedList<Message> messages = diskDataCenter.loadAllMessages(queue.getName());
            //恢复到消息与队列联系的map中
            queueMessageMap.put(queue.getName(),messages);
            //恢复到消息中心
            for (Message message : messages){
                addMessage(message);
            }

        }
    }

}
